package elasticsearch

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"strconv"

	"github.com/elastic/elastic-transport-go/v8/elastictransport"
	"github.com/elastic/go-elasticsearch/v8"
	"github.com/elastic/go-elasticsearch/v8/esapi"

	"mxshop_srvs/goods_srv/model"
)

type GoodsSearch interface {
	Insert(ctx context.Context, index string, goods model.ESGoods) error
	Query(ctx context.Context, index string, query map[string]interface{}) (Hit, error)
}

// goodsElasticSearch defines a ES repository.
type goodsElasticSearch struct {
	client *elasticsearch.Client
}

// NewGoodsES creates an ES repository.
func NewGoodsES(ctx context.Context, cli *elasticsearch.Client) (GoodsSearch, error) {
	goodsES := &goodsElasticSearch{
		client: cli,
	}

	mapping := model.ESGoods{}.GetMapping()
	// 先查询mapping是否存在
	exists, err := goodsES.IndexIsExists(ctx, []string{model.ESGoods{}.GetIndexName()})
	if err != nil {
		return nil, err
	}
	if exists {
		return &goodsElasticSearch{
			client: cli,
		}, nil
	}

	b, err := json.Marshal(mapping)
	if err != nil {
		return nil, err
	}

	err = goodsES.IndexCreate(ctx, model.ESGoods{}.GetIndexName(), b)
	if err != nil {
		return nil, err
	}

	return &goodsElasticSearch{
		client: cli,
	}, nil
}

type Hit struct {
	Value map[string]interface{}
}

func (h Hit) Total() float64 {
	return h.Value["total"].(map[string]interface{})["value"].(float64)
}

func (h Hit) Hits() []interface{} {
	return h.Value["hits"].([]interface{})
}

var NotFound = errors.New("not found")

func (e *goodsElasticSearch) Query(ctx context.Context, index string, query map[string]interface{}) (Hit, error) {
	var buf bytes.Buffer
	if err := json.NewEncoder(&buf).Encode(query); err != nil {
		log.Fatalf("Error encoding query: %s", err)
	}

	// Perform the search request.
	res, err := e.client.Search(
		e.client.Search.WithContext(ctx),
		e.client.Search.WithIndex(index),
		e.client.Search.WithBody(&buf),
		e.client.Search.WithTrackTotalHits(true),
		e.client.Search.WithPretty(),
	)
	if err != nil {
		return Hit{}, fmt.Errorf("error getting response: %s", err)
	}
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(res.Body)

	type H map[string]interface{}

	if res.IsError() {
		var e H
		if err := json.NewDecoder(res.Body).Decode(&e); err != nil {
			return Hit{}, fmt.Errorf("error parsing the response body: %s", err)
		} else {
			return Hit{}, fmt.Errorf("[%s] %s: %s",
				res.Status(),
				e["error"].(map[string]interface{})["type"],
				e["error"].(map[string]interface{})["reason"],
			)
		}
	}

	var r H
	if err := json.NewDecoder(res.Body).Decode(&r); err != nil {
		return Hit{}, fmt.Errorf("error parsing the response body: %s", err)
	}
	// total := int32(r["hits"].(map[string]interface{})["total"].(map[string]interface{})["value"].(float64))
	// goods := r["hits"].(map[string]interface{})["hits"].([]interface{})
	return Hit{Value: r["hits"].(map[string]interface{})}, nil
}

func (e *goodsElasticSearch) Insert(ctx context.Context, index string, goods model.ESGoods) error {
	b, err := json.Marshal(goods)
	if err != nil {
		return err
	}

	req := esapi.IndexRequest{
		DocumentID: strconv.Itoa(int(goods.ID)),
		Index:      index,
		Body:       bytes.NewBuffer(b),
	}
	res, err := req.Do(ctx, e.client)
	if err != nil {
		return err
	}
	defer func(body io.ReadCloser) {
		_ = body.Close()
	}(res.Body)

	if res.IsError() {
		return fmt.Errorf("failed to indexing document; response status: %s; id: %d", res.Status(), goods.ID)
	}
	return nil
}

func (e *goodsElasticSearch) IndexIsExists(ctx context.Context, index []string) (bool, error) {
	req := esapi.IndicesExistsRequest{
		Index: index,
	}

	res, err := req.Do(ctx, e.client)
	if err != nil {
		return false, err
	}
	defer func(body io.ReadCloser) {
		_ = body.Close()
	}(res.Body)
	if res.IsError() {
		return false, nil
	}
	return true, nil
}

// IndexCreate 创建Index
func (e *goodsElasticSearch) IndexCreate(ctx context.Context, index string, body []byte) error {
	req := esapi.IndicesCreateRequest{
		Index: index,
		Body:  bytes.NewBuffer(body),
	}

	res, err := req.Do(ctx, e.client)
	if err != nil {
		return err
	}
	defer func(body io.ReadCloser) {
		_ = body.Close()
	}(res.Body)
	if res.IsError() {
		return fmt.Errorf("failed to create index; response status: %s", res.Status())
	}
	return nil
}

func Init(addr []string) (*elasticsearch.Client, error) {
	esConf := elasticsearch.Config{
		Addresses: addr,
		Logger: &elastictransport.ColorLogger{
			Output:            os.Stdout, // 输出到终端
			EnableRequestBody: true,      // 打印请求体(方便查看请求)
		},
	}

	return elasticsearch.NewClient(esConf)
}
